﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.Linq;
using System.Data.Linq.SqlClient;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Web;
using jSLApp.Client.Workflow;
using jSLApp.Web.Workflow;
using jSLCore.Service.Security;
using jSLCore.Service.Workflow;
using jSLCore.Service.Workflow.Linq;
using Role = jSLApp.Web.Workflow.Role;
using User = jSLApp.Web.Workflow.User;

namespace jSLApp.Web
{
    [ServiceContract(Namespace = "")]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class MainService
    {
        //user it to check permissions
        private void CheckPermission(Permission permission, string faultText)
        {
            SecurityHelper.CheckHasPermission((int)permission, faultText);
        }
        
        internal static Film GetFilm(DB db,int id)
        {
            return db.Films.GetOneOrExc(id);
        }

        internal static User GetUser(DB db,string login)
        {
            return db.Users.GetOneOrExc(login, "Login");
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SetSelectedFilm(int id)
        {
            CheckPermission(Permission.SetSelectedFilm, "Вам необходимо право на установку избранного фильма");
            using (var db = new DB())
            {
                var film = GetFilm(db, id);

                var selected = db.Films.SingleOrDefault(f => f.IsSelected);
                if (selected != null) selected.IsSelected = false;
                film.IsSelected = true;

                LogEvent(db,LogType.UserAction, "Устновлен избранный фильм",id,null,GetUserNameOrExc());

                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public PaymentModule[] GetPaymentModules()
        {
            using (var db = new DB())
            {
                return db.PaymentModules
                    .Where(p=>p.IsEnabled)
                    .BeforeSending()
                    .ToArray();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Payment[] GetUserPayments(string userName)
        {
            using (var db = new DB())
            {
                var user = GetUser(db,userName);

                return user.Payments
                    .BeforeSending()
                    .ToArray();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void CheckOut(int filmId)
        {
            var userName = SecurityHelper.GetUserName();
            using (var db = new DB())
            {
                var user = GetUser(db,userName);

                var film = GetFilm(db,filmId);

                if (db.CheckOuts.Any(c=>c.Film==film && c.User==user))
                {
                    jWorkflowException.ThrowNew(string.Format("Фильм {0} уже куплен",film.Name));
                }

                try
                {
                    film.Buy(user);
                }
                catch (InvalidOperationException exception)
                {
                    jWorkflowException.ThrowNew(exception.Message);
                }
                
                LogEvent(db,LogType.UserAction, "Покупка фильма",filmId,null,user);

                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void DeleteTag(string tag)
        {
            using (var db = new DB())
            {
                db.Tags.DeleteAllOnSubmit(db.Tags.Where(t => SqlMethods.Like(t.Name, tag)));
                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        internal static Visit GetVisit(HttpRequest request,DB db)
        {
            var name = request.GetVisitName();

            var visit = db.Visits.SingleOrDefault(v=>SqlMethods.Like(v.Name,name));

            return visit;
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SiteVisit()
        {
            var userName = SecurityHelper.GetUserName();

            using (var db = new DB())
            {
                var user = db.Users.GetOneOrNull(userName,"Login");
             //   LogEvent(db,LogType.UserAction, "Посещение сайта",null,null,user);


                var visit = GetVisit(HttpContext.Current.Request,db);

                if(visit==null)
                {
                    visit = HttpContext.Current.Request.CreateVisit();
                    db.Visits.InsertOnSubmit(visit);
                }

                visit.SilverLightEnter = true;

                if(!visit.LoggedOn)
                {
                    visit.LoggedOn = user != null;
                }

                db.SubmitChanges();
            }
            
        }
        
        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public bool IsFilmReadyForCurrentUser(int filmId)
        {
            using (var db = new DB())
            {
                var film = GetFilm(db, filmId);
                return IsFilmAccessedByCurrentUser(db, film);
            }
        }

        public static bool IsFilmAccessedByCurrentUser(DB db, Film film)
        {
            if (film.Price == 0) return true;

            var userName = SecurityHelper.GetUserName();
            if (string.IsNullOrEmpty(userName)) return false;

            var user = GetUser(db,userName);

            if (SecurityHelper.HasPermission((int)Permission.SetFilmSources)) return true;

            return db.CheckOuts.Any(ch=>ch.User==user && ch.Film==film);
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public CheckOut[] GetUserCheckouts(string userName)
        {
            using (var db = new DB())
            {
                var user = GetUser(db,userName);
                return user.CheckOuts
                    .BeforeSending()
                    .ToArray();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public decimal GetUserBalance(string userName)
        {
            using (var db = new DB())
            {
                var user = GetUser(db,userName);
                return user.Balance;
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public decimal GetCurrentBalance()
        {
            var userName = SecurityHelper.GetUserName();
            using (var db = new DB())
            {
                var user = GetUser(db,userName);
                return user.Balance;
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public byte[] GetFilmLastPhoto(int id)
        {
            using (var db = new DB())
            {
                var film = GetFilm(db, id);

                var photo = film.Photos.LastOrDefault();
                if (photo == null) return null;
                return photo.Photo1.ToArray();
            }
        }

        /*[FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Film GetSelectedFilm()
        {
            using (var db = new DB())
            {
                var film = db.Films.SingleOrDefault(f=>f.IsSelected);

                if(film==null)jWorkflowException.ThrowNew("Не найдено ни одного избранного фильма");

                film.BeforeSending();

                film.PrepareBigImage();
                
                return film;
            }
        }*/

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public byte[] GetBigCover(int id)
        {
            using (var db =new DB())
            {
                var film = GetFilm(db,id);
                return film.Photo.ToArray();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Film[] GetTopFilms()
        {
            using (var db = new DB())
            {
                return db.Films
                    .OnlyPublic()
                    .OrderByDescending(f=>f.Rating)
                    .OrderByDescending(f=>f.IsModeratorRecomended)
                    .Take(30)
                    //.PrepareBigImage()
                    .BeforeSending()
                    .ToArray();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Film GetRandomFilm()
        {
            using (var db = new DB())
            {
                var count = db.Films.OnlyPublic().Count();

                var rand = new Random();
                var index = rand.Next(count);

                var film = db
                    .Films
                    .OnlyPublic()
                    .Skip(index)
                    .Take(1)
                    .SingleOrDefault();

                film.BeforeSending();
                return film;
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Film[] GetFilms(int[] ids)
        {
            using (var db= new DB())
            {
                var result = new List<Film>();

                foreach (var id in ids)
                {
                    var film = db.Films.GetOneOrNull(id);
                    if(film!=null)
                    {
                        film.BeforeSending();
                        result.Add(film);   
                    }
                }

                return result.ToArray();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Film CreateNewFilm(string name)
        {
            CheckPermission(Permission.CreateNewFilm, "У вас нет прав создавать новые фильмы");

            var userName = SecurityHelper.GetUserName();

            using (var db = new DB())
            {
                var user = GetUser(db,userName);

                var newFilm = MainServiceEx.CreateNewFilm(name, user);

                LogEvent(db,LogType.UserAction, "Создание фильма:"+name,null,null,user);

                db.SubmitChanges();
                db.Refresh(RefreshMode.OverwriteCurrentValues,newFilm);
                newFilm.BeforeSending();
                return newFilm;
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public int[] SearchFilms(string searchText, bool onlyByName,int? genreId)
        {
            using (var db = new DB())
            {
                IQueryable<Film> films = db.Films;

                if(genreId!=null)
                {
                    var genre = GetGenre(db,(int) genreId);

                    films = films.Where(f => f.Genre == genre);
                }

                if(!SecurityHelper.HasPermission((int) Permission.CreateNewFilm))
                {
                    films = films.OnlyPublic();
                }

                return films
                    .Search(searchText, onlyByName)
                    .ToArray();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Film GetFilm(int id)
        {
            Film film=null;
            using (var db = new DB())
            {
                film = GetFilm(db,id);
                film.PrepareBigImage();
                film.BeforeSending();
            }


             using (var db = new DB())
            {
                var film2 = GetFilm(db,id);

                var visit = GetVisit(HttpContext.Current.Request,db);

                if(visit!=null)
                {
                    new LookAt() {Film = film2, Visit = visit};
                    db.SubmitChanges();
                }
            }

            return film;
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SetFilmName(int id,string name)
        {
            CheckPermission(Permission.ChangeFilmName, "У вас нет права изменять название фильма");
            using (var db = new DB())
            {
                var film = GetFilm(db,id);

                film.Name = name;

                LogEvent(db,LogType.UserAction, "Изменение названия фильма=>"+name,id,null);

                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SetFilmDescription(int id, string description)
        {
            using (var db = new DB())
            {
                var film = GetFilm(db, id);

                film.Description = description;

                LogEvent(db, LogType.UserAction, "Изменение описания фильма=>" + description, id, null);
                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SetFilmCountry(int id,string coutry)
        {
            using (var db = new DB())
            {
                var film = GetFilm(db, id);

                film.Country = coutry;

                LogEvent(db,LogType.UserAction, "Изменение страны фильма=>" + coutry,id,null);
                db.SubmitChanges();
            }
        }


        /// Размер обложки DVD диска - 5,1305 дюйма , 7,188 дюйма
        /// Будем использовать большой размер длина 200 , высота - 280
        public const int BigCoverWidth = 400;
        public const int BigCoverHeight = 560;

        public const int SmallCoverWidth = 60;
        public const int SmallCoverHeight = 84;

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public byte[] ChangeFilmImage(int filmId,byte[] bs)
        {
            CheckPermission(Permission.ChangeFilmImage, "У вас нет права на изменение ковера фильма");
            using (var db = new DB())
            {
                var film = GetFilm(db,filmId);

                byte[] bigCover = null;
                byte[] smallCover = null;
                try
                {
                    bigCover = bs.ConvertImageToPng(BigCoverWidth,BigCoverHeight);
                    smallCover = bs.ConvertImageToPng(SmallCoverWidth,SmallCoverHeight);

                    film.Photo = new Binary(bigCover);
                    film.SmallCover = new Binary(smallCover);
                }
                catch (Exception)
                {
                    jWorkflowException.ThrowNew("Не удалось прочесть файл изображения");
                }

                LogEvent(db, LogType.UserAction, "Изменение обложки фильма", filmId, null);
                db.SubmitChanges();

                return bigCover;
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public bool GetAgeOfCurrentUser()
        {
            var userName = GetUserNameOrWorkflowException("Не удается определить возрастную группу пользоватлея");
            using (var db = new DB())
            {
                var user = GetUser(db,userName);
                return user.Age;
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SetAgeOfCurrentUser()
        {
            var userName = GetUserNameOrWorkflowException("Не удается определить возрастную группу пользоватлея");
            using (var db = new DB())
            {
                var user = GetUser(db, userName);
                user.Age = true;

                LogEvent(db, LogType.UserAction, "Пользователь подтвердил возраст 18+",null, null);
                db.SubmitChanges();
            }
        }
        
        public const int PosterMaxWidth = 1280;

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Photo SetImageScreen(int filmId,byte[] bs)
        {
            CheckPermission(Permission.ChangeImageScreen, "Нет прав для изменения постера фильма");
            using (var db = new DB())
            {
                var film = GetFilm(db, filmId);

                byte[] convertImageToPng = null;
                try
                {
                    var bitmap = bs.ToBitmap();
                    int width;
                    int height;


                    if(bitmap.Width<=PosterMaxWidth)
                    {
                        width = bitmap.Width;
                        height = bitmap.Height;
                    }
                    else
                    {
                        var k = (double)bitmap.Width/PosterMaxWidth;
                        width = PosterMaxWidth;
                        height = (int) (bitmap.Height / k);
                    }

                    convertImageToPng = bs.ConvertImageToPng(width,height);
                }
                catch (Exception)
                {
                    jWorkflowException.ThrowNew("Не удалось прочесть файл изображения");
                }

                
                db.Photos.DeleteAllOnSubmit(film.Photos.AsEnumerable());//Все другие удаляем

                var photo = new Photo { Film = film, Photo1 = new Binary(convertImageToPng) };
                db.Photos.InsertOnSubmit(photo);

                LogEvent(db, LogType.UserAction, "Изменение постера фильма" ,filmId, null);
                db.SubmitChanges();

                db.Refresh(RefreshMode.OverwriteCurrentValues,photo);

                return photo;
            }
        }

        internal static Photo GetPhoto(DB db,int photoId)
        {
            return db.Photos.GetOneOrExc(photoId);
        }

        /*[FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void DeletePhoto(int photoId)
        {
            using (var db = new DB())
            {
                var photo = GetPhoto(db,photoId);
                db.Photos.DeleteOnSubmit(photo);
                db.SubmitChanges();
            }
        }*/

        internal static Genre GetGenre(DB db,int genreId)
        {
            return db.Genres.GetOneOrExc(genreId);
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void DeleteGenre(int id)
        {
            CheckPermission(Permission.DeleteGenre, "У вас не права удалять жанры");
            using (var db = new DB())
            {
                var genre = GetGenre(db,id);
                db.Genres.DeleteOnSubmit(genre);
                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Genre AddGenre(string name)
        {
            CheckPermission(Permission.CreateNewGenre, "У вас не права создавать жанры");
            using (var db = new DB())
            {
                var genre = new Genre();
                genre.Name = name;
                db.Genres.InsertOnSubmit(genre);

                LogEvent(db, LogType.UserAction, "Создание жанра: " + name, null, null);

                db.SubmitChanges();

                db.Refresh(RefreshMode.OverwriteCurrentValues,genre);
                genre.BeforeSending();

                return genre;
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Genre[] GetGenres()
        {
            using (var db = new DB())
            {
                return db.Genres
                    .BeforeSending()
                    .ToArray();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Film[] GetRelated(int filmId)
        {
            using (var db = new DB())
            {
                var film = GetFilm(db,filmId);

                if (film.Tags != null)
                {
                    var tags = film.Tags.Split(new[]{Constants.TagDelimiter});

                    return tags
                        .SelectMany(tag => db.Films.Where(f=>f.id!=filmId).ByTag(tag))
                        .GroupBy(f => f)
                        .Select(g => new {g.Key, count = g.Count()})
                        .OrderByDescending(r => r.count)
                        .Take(3)
                        .Select(g => g.Key)
                        .PrepareBigImage()
                        .BeforeSending()
                        .ToArray();
                }
                return Enumerable
                    .Empty<Film>()
                    .ToArray();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SetFilmYear(int id, int year)
        {
            CheckPermission(Permission.ChangeFilmAdvanced, "У вас не права изменять год издания фильма");
            using (var db = new DB())
            {
                var film = GetFilm(db, id);

                film.Year = year;

                LogEvent(db, LogType.UserAction, "Изменение года фильма=>" + year, id, null);
                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SetFilmGenre(int id, int genreId)
        {
            CheckPermission(Permission.ChangeFilmAdvanced, "У вас не права изменять жанр фильма");
            using (var db = new DB())
            {
                var film = GetFilm(db, id);
                var genre = GetGenre(db,genreId);
                film.Genre = genre;
                LogEvent(db, LogType.UserAction, "Изменение жанра фильма" + genre.Name, id, null);
                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SetFilmPrice(int id, decimal price)
        {
            CheckPermission(Permission.ChangeFilmAdvanced, "У вас не права изменять цену фильма");
            using (var db = new DB())
            {
                var film = GetFilm(db, id);

                film.Price = price;

                LogEvent(db, LogType.UserAction, "Изменение цены фильма=>" + price, id, null);
                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SetFilmIsAgeNeed(int id, bool needAge)
        {
            CheckPermission(Permission.ChangeFilmAdvanced, "У вас не права изменять настройки ограничения по возрасту");
            using (var db = new DB())
            {
                var film = GetFilm(db, id);

                film.AgeNeed = needAge;
                LogEvent(db, LogType.UserAction, "Изменение возрастного ограничения фильма=>" + (needAge?"Ограничение по возрасту":"Нет ограничения по возрасте"), id, null);
                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SubmitPasswordChange(Guid pwdCode, string newPwd)
        {
            using (var db = new DB())
            {
                var user = db.Users.SingleOrDefault(u => u.ChangePasswordCode == pwdCode);
                if (user == null) jWorkflowException.ThrowNew("Неверный проверочный код для смены пароля. Возможно вы уже использовали его.");

                user.ChangePasswordCode = null;
                user.Password = new Binary(SecurityHelper.GetPasswordHash(newPwd));

                LogEvent(db, LogType.UserAction, "Изменения пароля пользователя", null, null, user);
                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SubmitEMail(Guid code)
        {
            using (var db = new DB())
            {
                var user = db.Users.SingleOrDefault(u => u.VerifyCode == code);
                if (user == null) jWorkflowException.ThrowNew("Пользователь не найден. Возможно вы слишком поздно подтвердили вашу заявку на регистрацию. Попытайтесь повторить процедуру регистрации");

                if (!user.emailAccepted)//auto unlock, but only once,so disabled users can't unlock by submiting email again
                {
                    user.IsEnabled = true;
                }
                user.emailAccepted = true;

                LogEvent(db, LogType.UserAction, "Пользователь подтвердил адрес электронной почты", null, null, user);

                db.SubmitChanges();

                /*SendSystemMail(db, "Пользователь {0} подтвердил свой адрес электронной почты:{1}", user.Login.Trim(),
                               user.email);*/
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SetFilmIsSmoothStreaming(int id, bool isSmoothStreaming)
        {
            CheckPermission(Permission.ChangeFilmAdvanced, "У вас нет права изменять адаптивность видео");
            using (var db = new DB())
            {
                var film = GetFilm(db, id);

                film.IsSmoothStreaming = isSmoothStreaming;
                LogEvent(db, LogType.UserAction, "Изменение адаптивности источника фильма=>" + (isSmoothStreaming?"Адаптивный":"Прогрессивный"), id, null);
                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public string[] GetAllTags()
        {
            using (var db = new DB())
            {
                return db.Tags
                    .BeforeSending()
                    .Select(t => t.Name)
                    .ToArray();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SetFilmTags(int id, string tags)
        {
            CheckPermission(Permission.ChangeFilmAdvanced, "У вас не права изменять тэги фильма");
            using (var db = new DB())
            {
                var film = GetFilm(db, id);

                film.Tags = tags.ToUpper();

                var tagsEntityes = tags.Split(new []{Constants.TagDelimiter});

                foreach (var tagsEntitye in tagsEntityes)
                {
                    var entitye = tagsEntitye;
                    if (!db.Tags.Any(t => t.Name == entitye))
                    {
                        db.Tags.InsertOnSubmit(new Tag {Name = tagsEntitye.ToUpper()});
                    }
                }

                LogEvent(db, LogType.UserAction, "Изменение тэгов фильма=>" + tags, id, null);

                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public bool CanRate(int filmId)
        {
            var userName = SecurityHelper.GetUserName();
            using (var db = new DB())
            {
                if (string.IsNullOrEmpty(userName)) return false;

                var user = GetUser(db,userName);
                
                var film = GetFilm(db,filmId);

                return !film.Comments.Any(c=>c.User==user && c.Rating!=null);
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public double AddRating(int filmdId,double rate)
        {
            var userName = SecurityHelper.GetUserName();

            using (var db = new DB())
            {
                var user = GetUser(db,userName);
                var film = GetFilm(db,filmdId);

                var count = film.Comments.Count(c=>c.Rating!=null)+1;

                new Comment {CreationDate = DateTime.Now,Film = film,User = user,Rating = rate};

                var everage = ((film.Rating*(count-1)+rate))/count;

                film.Rating = everage;
                LogEvent(db, LogType.UserAction, "Пользователь оценил фильм=>" + rate, filmdId, null);
                db.SubmitChanges();
                return everage;
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void DeleteComment(int commentId)
        {
            CheckPermission(Permission.DeleteComment, "У вас не права удалять комментарии");
            using (var db = new DB())
            {
                var comment = GetComment(db,commentId);
                db.Comments.DeleteOnSubmit(comment);

                LogEvent(db, LogType.UserAction, "Модератор удалил комментарий", commentId, null);
                db.SubmitChanges();
            }
        }

        internal static Comment GetComment(DB db,int commentId)
        {
            return db.Comments.GetOneOrExc(commentId);
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Comment AddComment(int filmdId,string content)
        {
            var userName = SecurityHelper.GetUserName();
            
            using (var db = new DB())
            {
                var user = GetUser(db,userName);
                var film = GetFilm(db,filmdId);

                var comment = new Comment(){Content = content,CreationDate = DateTime.Now,Film = film,User = user,};

                LogEvent(db, LogType.UserAction, "Пользователь прокоментировал фильм фильм=>" + content, filmdId, null);
                db.SubmitChanges();
                db.Refresh(RefreshMode.OverwriteCurrentValues,comment);

                comment.BeforeSending();
                return comment;
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Comment[] GetComments(int filmId)
        {
            using (var db = new DB())
            {
                var film = GetFilm(db,filmId);
                return film.Comments.
                    Where(c=>c.Content!=null)
                    .OrderByDescending(c=>c.CreationDate)
                    .BeforeSending()
                    .ToArray();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SetFilmIsRecomended(int id, bool isRecomended)
        {
            CheckPermission(Permission.ChangeFilmAdvanced, "У вас не права изменять настройки рекомендации");
            using (var db = new DB())
            {
                var film = GetFilm(db, id);

                film.IsModeratorRecomended = isRecomended;
                LogEvent(db, LogType.UserAction, "Модератор установил рекомендацию фильму=>" + (isRecomended?"Рекомендован":"Обычный"), id, null);
                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SetFilmIsPublic(int id, bool isPublic)
        {
            CheckPermission(Permission.ChangeFilmAdvanced, "У вас нет прав изменять настройки публикации");
            using (var db = new DB())
            {
                var film = GetFilm(db, id);

                film.IsPublic = isPublic;
                LogEvent(db, LogType.UserAction, "Пользователь установил публичность фильма=>" + (isPublic?"Публичный":"Скрытый"), id, null);
                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SetFilmSource(int id, string source)
        {
            CheckPermission(Permission.SetFilmSources, "У вас не права задавать источник фильма");
            using (var db = new DB())
            {
                var film = GetFilm(db, id);

                film.SourceUri = source;

                LogEvent(db, LogType.UserAction, "Пользователь изменил источник фильма=>" + source, id, null);
                db.SubmitChanges();
            }
        }

        internal string GetUserNameOrWorkflowException(string errorText)
        {
            var userName = SecurityHelper.GetUserName();

            if (string.IsNullOrEmpty(userName)) jWorkflowException.ThrowNew(errorText);
            return userName;
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SendFeedback(string text)
        {
            using (var db = new DB())
            {
                SendMailToAllSuperVisors(db, text);
            }
        }

        internal static void SendMailToAllSuperVisors(DB db, string text)
        {
            bool sent = false;
            var supervisors = db.Users.Where(u=>u.IsEnabled).ToArray().Where(u=>SecurityHelper.IsUserHasPermission(u.Login,(int) Permission.SuperVisor));
            foreach (var user in supervisors)
            {
                try
                {
                    SendMail(user.email, false, "Вам отправлено анонимное сообщение с сайта: {0}", text);
                    sent = true;
                }
                catch (SmtpFailedRecipientsException)
                {
                    LogEvent(db,LogType.System, "Отправка обратной связи не удалась для пользователя "+user.Login.Trim());
                }
            }

            if (!sent)
                throw new InvalidOperationException("Не удалось отправить обратную связь ни одному из получателей");
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SendMailToUser(string login, string message)
        {
            string userName = GetUserNameOrWorkflowException("Анонимный пользователь не может посылать письма другим пользователям");
            using (var db = new DB())
            {
                var user = GetUser(db, login);
                var sendUser = GetUser(db, userName);

                string res =
                    string.Format(
                        @"Вам отправлено письмо от пользователя {0}({2}):

{1}

Вы можете ответить на адрес {3}",
                        userName, message, sendUser.NameText, sendUser.email);

                SendMail(user.email, res);
            }
        }

        private void SendMail(string to, string what, params object[] pars)
        {
            SendMail(to, false, what, pars);
        }

        internal static void SendMail(string to, bool _catch, string what, params object[] pars)
        {
            var props = ConfigurationManager.AppSettings;
            var client = new SmtpClient(props["SmtpServer"])
            {
                DeliveryMethod = SmtpDeliveryMethod.Network,
                Credentials = new NetworkCredential(props["SmtpLogin"], props["SmtpPwd"])
            };

            what = what + @"
С Уважением, Почтовая служба " + props["HostAddress"];

            try
            {
#if !DEBUG
                client.Send(props["SmtpLogin"],
                    to,
                    "Videorent",
                    string.Format(what, pars));
#endif
            }
            catch (SmtpFailedRecipientsException)
            {
                if (!_catch)
                    jWorkflowException.ThrowNew("Не удалось отправить письмо следующим получателям: " + to);
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SetFilmTrailer(int id, string source)
        {
            CheckPermission(Permission.SetFilmSources, "У вас не права изменять трейлер фильма");

            using (var db = new DB())
            {
                var film = GetFilm(db, id);

                film.TrailerUri = source;
                LogEvent(db, LogType.UserAction, "Пользователь изменил источник трейлера фильма=>" + source, id, null);
                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SetFilmEnName(int id,string enName)
        {
            CheckPermission(Permission.ChangeFilmName, "У вас нет права изменять название фильма");

            using (var db = new DB())
            {
                var film = GetFilm(db, id);
                film.EnName = enName;
                LogEvent(db, LogType.UserAction, "Пользователь изменил латинское название фильма=>" + enName, id, null, SecurityHelper.GetUserName());
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void DeleteUser(string login)
        {
            CheckPermission(Permission.UserAdmin, "У вас не права на удаление пользователя");

            using (var db = new DB())
            {
                var user = GetUser(db, login);
                db.Users.DeleteOnSubmit(user);

                SendMail(user.email, @"Ваш аккаунт {0} удален.", login.Trim());

            

                LogEvent(db, LogType.UserAction, "Удаление пользователя: " + user.Login.Trim(), null, null, GetUserNameOrExc());

                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void ManualAcceptMail(string login)
        {
            CheckPermission(Permission.UserAdmin, "Необходимы права администратора");
            using (var db = new DB())
            {
                var user = GetUser(db, login);
                user.emailAccepted = true;
                user.IsEnabled = true;

                LogEvent(db, LogType.UserAction, "Пользователь разблокирован: " + login.Trim(), null, null, GetUserNameOrExc());

                db.SubmitChanges();

                SendMail(user.email, true, "Ваш e-mail подтвержден и аккаунт разблокирован");
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void ResetPassword(string login, string mail)
        {

            using (var db = new DB())
            {
                var user = GetUser(db, login);
                if (user.email.Trim().ToUpper() != mail.Trim().ToUpper())
                {
                    jWorkflowException.ThrowNew("Не найден пользователь с такими логин и Email");
                }
                if (!user.emailAccepted)
                {
                    jWorkflowException.ThrowNew("Ваш email не подтвержден после регистрации.");
                }

                var pwdCode = Guid.NewGuid();
                user.ChangePasswordCode = pwdCode;

                LogEvent(db, LogType.UserAction, "Пользователь отправил запрос на изменения пароля", null, null, user);
                db.SubmitChanges();

                var props = ConfigurationManager.AppSettings;

                SendMail(user.email, @"Кто-то послал запрос на изменение вашего пароля.
Для изменения пароля перейдите по ссылке ниже
{0}/{1}?{3}={2}
Если вы не посылали запрос за изменения пароля - просто проигнорируйте данное сообщение",
                                                                                        props["HostAddress"],
                                                                                        props["PageAddress"],
                                                                                        pwdCode,
                                                                                        PageNames.PwdChangeVerificationCode);
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void Registration(string login, string pwd, string mail)
        {
            using (var db = new DB())
            {
                if (db.Users.Any(u => u.Login == login)) jWorkflowException.ThrowNew("Пользователь с таким логином уже существует");
                if (db.Users.OnlyValidMail().Any(u => SqlMethods.Like(u.email, mail))) jWorkflowException.ThrowNew("Пользователь с таким e-mail уже существует");
                var verCode = Guid.NewGuid();
                var defaultRole = db.Roles.GetOne(new Guid("4734ff64-ce1b-48c6-a11a-6039d686f3fb"));//todo: dirty hack
                var user = new User
                               {
                                   email = mail,
                                   CreationDate = DateTime.Now,
                                   Login = login,
                                   Password = new Binary(SecurityHelper.GetPasswordHash(pwd)),
                                   VerifyCode = verCode,
                                   Role = defaultRole,
                                   emailAccepted = false,
                };

                var props = ConfigurationManager.AppSettings;
#if !DEBUG
                SendMail(mail,@"Здравствуйте.
Ваш адрес был указан в качестве электронной почты для регистрации 
на сайте {0}.
Для подтверждения регистрации выделите ссылку ниже и вставьте ее в ваш web браузер.
{1}/{2}?{4}={3}
Если вы не регистрировались на сайте - просто проигнорируйте сообщение.", props["HostAddress"], props["HostAddress"], props["PageAddress"], verCode,PageNames.EmailVerificationCode);
#endif
                db.Users.InsertOnSubmit(user);

                LogEvent(db, LogType.System, string.Format("Пользователь зарегистрировался: {0} Логин:{1}", user.email, user.Login.Trim()));

                db.SubmitChanges();

                //SendSystemMail(db, "Пользователь зарегистрировался: {0} Логин:{1}",user.email,user.Login.Trim());
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public SystemLog[] GetLogs()
        {
            CheckPermission(Permission.UserAdmin, "У вас нет прав просматривать журнал событий");

            using (var db = new DB())
            {
                return db.SystemLogs.OrderByDescending(lg => lg.CreationDate).Take(200).BeforeSending().ToArray();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Role[] GetAllRoles()
        {
            //CheckPermission(Permission.ChangeUserEx, "Необходимы права супер-пользователь");
            using (var db = new DB())
            {
                return db.Roles
                    .BeforeSending()
                    .ToArray();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void SetIsEnabled(string login, bool isEnabled)
        {
            CheckPermission(Permission.UserAdmin, "Нет прав для работы с пользователями");
            using (var db = new DB())
            {
                var user = GetUser(db, login);
                /*if (user.IsSuperVisor()
                   ||
                   user.IsAdministrator())
                {
                    CheckPermission(Permission.ChangeUserEx, "Нет прав для изменения параметров этой группы пользователей");
                }*/
                user.IsEnabled = isEnabled;
                if (isEnabled)
                {
#if !DEBUG
                    SendMail(user.email, @"Ваш аккаунт {0} активизирован.", login.Trim());
#endif
                }

                LogEvent(db, LogType.UserAction, "Активизация аккаунта: " + login.Trim(), null, null, user);

                db.SubmitChanges();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void RoleChange(string login, Guid newRole)
        {
            CheckPermission(Permission.UserAdmin, "Нет прав для работы с пользователями");
            using (var db = new DB())
            {
                var user = GetUser(db, login);
               /* if (user.IsSuperVisor()
                    ||
                    user.IsAdministrator())
                {
                    CheckPermission(Permission.ChangeUserEx, "Нет прав для изменения параметров этой группы пользователей");
                }*/
                var role = db.Roles.GetOneOrjWorkflowException(newRole, "Роль", "");
                user.Role = role;

                LogEvent(db, LogType.UserAction, "Изменена роль пользователя: " + user.Login.Trim() + " =>" + role.Name, null, null, GetUserNameOrExc());
                db.SubmitChanges();
            }
        }

        private string GetUserNameOrExc()
        {
            var userName = SecurityHelper.GetUserName();

            if (string.IsNullOrEmpty(userName)) throw new InvalidOperationException();
            return userName;
        }

        internal static void LogEvent(DB db, LogType logType, string info, int? intId = null, Guid? guidId = null, User user = null)
        {
            var systemLog = new SystemLog { CreationDate = DateTime.Now, Info = info, RowGUIDId = guidId, RowIntId = intId, User1 = user, Type = (int)logType };

            db.SystemLogs.InsertOnSubmit(systemLog);
        }

        internal void LogEvent(DB db, LogType logType, string info, int? intId, Guid? guidId)
        {
            LogEvent(db, logType, info, intId, guidId, GetUser(db, SecurityHelper.GetUserName()));
        }

        internal void LogEvent(DB db, LogType logType, string info, int? intId, Guid? guidId, string user)
        {
            LogEvent(db, logType, info, intId, guidId, GetUser(db, user));
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public User GetUser(string login)
        {
            using (var db = new DB())
            {
                var user = GetUser(db, login);

                user.BeforeSending();
                return user;
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public User[] SearchUser(string searchText, bool showUnaccepted)
        {
            CheckPermission(Permission.UserAdmin, "Нет прав для работы с пользователями");
            using (var db = new DB())
            {
                //if one user is found, and it's blocked
                var badUser = db.Users.SingleOrDefault(u => u.Login == searchText && !u.emailAccepted);
                if (badUser != null)
                {
                    jWorkflowException.ThrowNew("Найден один пользователь с указанным логином, но его e-mail не подтвержден: " + badUser.email);
                }

                var result = db.Users.AsQueryable();
                if (!showUnaccepted) result = result.OnlyValidMail();
                if (!string.IsNullOrEmpty(searchText))
                {
                    result = result.BySearchText(searchText);
                }

                return result
                    .BeforeSending()
                    .ToArray();
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void DeleteFilm(int id)
        {
            CheckPermission(Permission.DeleteFilm, "У вас не прав на удаление фильма");

            using (var db = new DB())
            {
                var film = GetFilm(db,id);

                db.CheckOuts.DeleteAllOnSubmit(film.CheckOuts);
                db.Comments.DeleteAllOnSubmit(film.Comments);
                db.Photos.DeleteAllOnSubmit(film.Photos);
                db.Films.DeleteOnSubmit(film);

                LogEvent(db,LogType.UserAction,"Удаление фильма: "+film.Name,id,null);

                db.SubmitChanges();
                
            }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public FileEntry[] GetFreeFiles()
        {
            CheckPermission(Permission.SetFilmSources, "У вас нет права работать с источником медиа");
            using (var db = new DB())
            {
                var result = new List<FileEntry>();

                var files = Directory.GetFiles(MoviesFolder,"*.wmv");

                foreach (var file in files)
                {
                    var fileName = Path.GetFileName(file);

                    if (!db.Films.Any(f => SqlMethods.Like(f.SourceUri, fileName) || SqlMethods.Like(f.TrailerUri, fileName)))
                    {
                        var fileInfo = new FileInfo(file);
                        result.Add(new FileEntry(){Name = fileInfo.Name,Size = fileInfo.Length});
                    }
                }

                return result.ToArray();
            }
        }

        internal static string MoviesFolder
        {
            get { return Path.Combine(ServerHelper.CurrentWebApplicationPhysycalPath, ""); }
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public void DeleteFile(string fileName)
        {
            CheckPermission(Permission.SetFilmSources, "У вас нет права работать с источником медиа");

            try
            {
                File.Delete(Path.Combine(MoviesFolder, fileName));
                using (var db = new DB())
                {
                    LogEvent(db, LogType.UserAction, "Пользователь удаляет файл=>" + fileName, null, null);
                }
            }
            catch (Exception exception)
            {
                jWorkflowException.ThrowNew(exception.Message);
            }
            
        }

        [FaultContract(typeof(jWorkflowException))]
        [OperationContract]
        public Photo[] GetPhotos(int filmId)
        {
            using (var db = new DB())
            {
                var film = GetFilm(db,filmId);

                return film.Photos
                    .BeforeSending()
                    .ToArray();
            }
        }

        /**/
    }
}
